import { clamp } from '@notes/global/utils';

type CollisionBox = {
    /**
     * The point that the objRect is positioned to.
     */
    positioningPoint: { x: number; y: number };
    /**
     * The boundary rect of the obj that is being positioned.
     */
    objRect?: { height: number; width: number };
    /**
     * The boundary rect of the container that the obj is in.
     */
    boundaryRect?: DOMRect;
    offsetX?: number;
    offsetY?: number;
    edgeGap?: number;
};

export function calcSafeCoordinate({
    positioningPoint,
    objRect = { width: 0, height: 0 },
    boundaryRect = document.body.getBoundingClientRect(),
    offsetX = 0,
    offsetY = 0,
    edgeGap = 20,
}: CollisionBox) {
    const safeX = clamp(
        positioningPoint.x + offsetX,
        edgeGap,
        boundaryRect.width - objRect.width - edgeGap
    );
    const y = positioningPoint.y + offsetY;
    // Not use clamp for y coordinate to avoid the quick bar always showing after scrolling
    // const safeY = clamp(
    //   positioningPoint.y + offsetY,
    //   edgeGap,
    //   boundaryRect.height - objRect.height - edgeGap
    // );
    return {
        x: safeX,
        y,
    };
}

/**
 * Used to compare the space available
 * at the top and bottom of an element within a container.
 *
 * Please give preference to {@link getPopperPosition}
 */
export function compareTopAndBottomSpace(
    obj: { getBoundingClientRect: () => DOMRect },
    container = document.body,
    gap = 20
) {
    const objRect = obj.getBoundingClientRect();
    const spaceRect = container.getBoundingClientRect();
    const topSpace = objRect.top - spaceRect.top;
    const bottomSpace = spaceRect.bottom - objRect.bottom;
    const topOrBottom: 'top' | 'bottom' =
        topSpace > bottomSpace ? 'top' : 'bottom';
    return {
        placement: topOrBottom,
        // the height is the available space.
        height: (topOrBottom === 'top' ? topSpace : bottomSpace) - gap,
    };
}

/**
 * Get the position of the popper element with flip.
 */
export function getPopperPosition(
    popper: {
        getBoundingClientRect: () => DOMRect;
    },
    reference: {
        getBoundingClientRect: () => DOMRect;
    },
    { gap = 12, offsetY = 5 }: { gap?: number; offsetY?: number } = {}
) {
    if (!popper) {
        // foolproof, someone may use element with non-null assertion
        console.warn(
            'The popper element is not exist. Popper position maybe incorrect'
        );
    }
    const { placement, height } = compareTopAndBottomSpace(
        reference,
        document.body,
        gap + offsetY
    );

    const referenceRect = reference.getBoundingClientRect();
    const positioningPoint = {
        x: referenceRect.x,
        y: referenceRect.y + (placement === 'bottom' ? referenceRect.height : 0),
    };

    // TODO maybe use the editor container as the boundary rect to avoid the format bar being covered by other elements
    const boundaryRect = document.body.getBoundingClientRect();
    // Note: the popperRect.height maybe incorrect
    // because we are calculated its correct height
    const popperRect = popper?.getBoundingClientRect();

    const safeCoordinate = calcSafeCoordinate({
        positioningPoint,
        objRect: popperRect,
        boundaryRect,
        offsetY: placement === 'bottom' ? offsetY : -offsetY,
    });

    return {
        placement,
        /**
         * The height is the available space height.
         *
         * Note: it's a max height, not the real height,
         * because sometimes the popper's height is smaller than the available space.
         */
        height,
        x: `${safeCoordinate.x}px`,
        y:
            placement === 'bottom'
                ? `${safeCoordinate.y}px`
                : // We need to use `calc(-100%)` since the height of popper maybe incorrect
                `calc(${safeCoordinate.y}px - 100%)`,
    };
}
